package com.leedarson.utils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;

import freemarker.core.JSONOutputFormat;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * 生成器的作用是配置，主要的参数配置工作应该再生成器中完成。<br>
 *
 * @author howard
 * @Create_Date: 2020年5月25日下午2:00:41
 * @Modified_By: howard
 * @Modified_Date: 2020年5月25日下午2:00:41
 * @Why_and_What_is_modified: <br>
 */
@Accessors(chain = true)
public class LeedarsonGenerator {
	private static final Logger logger = LoggerFactory.getLogger(LeedarsonGenerator.class);

	/**
	 * 配置信息
	 */
	protected ConfigBuilder config;
	/**
	 * 注入配置
	 */
	@Getter(AccessLevel.NONE)
	@Setter(AccessLevel.NONE)
	protected InjectionConfig injectionConfig;
	/**
	 * 数据源配置
	 */
	private DataSourceConfig dataSource;
	/**
	 * 数据库表配置
	 */
	private StrategyConfig strategy;
	/**
	 * 包 相关配置
	 */
	private PackageConfig packageInfo;
	/**
	 * 模板 相关配置
	 */
	private TemplateConfig template;
	/**
	 * 全局 相关配置
	 */
	private GlobalConfig globalConfig;
	/**
	 * 模板引擎
	 */
	private AbstractTemplateEngine templateEngine;

//	public static JSONObject frontsideConfig;

	/**
	 * 通过设定的配置获得数据库表信息<br>
	 *
	 * @auhor howard
	 * @Create_Date: 2020年9月23日下午3:13:02
	 * @last_modify by howard at 2020年9月23日下午3:13:02 <br>
	 * @Why_and_What_is_modified:
	 * @return
	 */
	public List<TableInfo> getTableList() {
		config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);
		if (null != injectionConfig) {
			injectionConfig.setConfig(config);
		}
		List<TableInfo> tablelist = config.getTableInfoList();
		return tablelist;
	}

	/**
	 * 生成代码
	 */
	public void execute() {
		logger.debug("==========================准备生成文件...==========================");
		// 初始化配置
		// 因为在展示列表的时候，onfig已经被初始化一次，这一次需要重新初始化以过滤掉未勾选的表。
//		if (null == config) {
		config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);
		if (null != injectionConfig) {
			injectionConfig.setConfig(config);
		}
//		}
		// 这里已经可以获得表的详细信息，提供给前端页面勾选，生成针对的列表等组件页面。
//		做一次控制台打印方便调试
		List<TableInfo> tablelist = config.getTableInfoList();
		tablelist.forEach(t -> {
			String tablename = t.getName();
			List<TableField> fields = t.getFields();
			System.out.println("拥有表：" + tablename);
			fields.forEach(f -> {
				String name = f.getName();
				String comment = f.getComment();
				String type = f.getColumnType().getType();
				System.out.println("字段：" + name + "   类型为：" + type + "   注释为：" + comment);
			});
		});
		//
		// 模块 dept_cost 结果展示条件。
		if (null == templateEngine) {
			// 为了兼容之前逻辑，采用 Velocity 引擎 【 默认 】
			templateEngine = new VelocityTemplateEngine();
		}
		ConfigBuilder pretreatmentConfigBuilder = this.pretreatmentConfigBuilder(config);
		// 这里是不破坏源码执行顺序，且加入前端配置最合适的地方。
//		在这里先进行前端文件夹路径的设置。方便后续engine.mkdirs();方法进行路径构建
		addFrontendPathInfo(config.getPathInfo());
		// 模板引擎初始化执行文件输出
		// 源码写的比较乱，且不好扩展。
		AbstractTemplateEngine engine = templateEngine.init(pretreatmentConfigBuilder);
		engine.mkdirs();// 根据 new ConfigBuilder初始化后的配置，pathinfo生成路径
		engine.batchOutput();// 这里是开始生成具体的java,xml等文件
		engine.open();// 批处理命令，理解为生成后的路径测试是否存在。
		logger.debug("==========================文件生成完成！！！==========================");
	}

	/**
	 * 增加前端文件的架构路径信息<br>
	 *
	 * @auhor howard
	 * @Create_Date: 2020年5月20日上午11:05:26
	 * @last_modify by howard at 2020年5月20日上午11:05:26 <br>
	 * @last_modify by howard at 2020年5月21日下午1:56:54 <br>
	 * @Why_and_What_is_modified: 修改一下，增加路径之间的关联，方便后续扩展和代码健壮性
	 * @param pathInfo
	 */
	private void addFrontendPathInfo(Map<String, String> pathInfo) {
		// 根据java文件路径生成前端文件路径
		String frontEndPath = org.springframework.util.StringUtils.replace(this.globalConfig.getOutputDir(),
				"/src/main/java", "/frontend");
		//
		JSONObject frontEndPathJson = new JSONObject();
		frontEndPathJson.put("build", frontEndPath + "/build");
		frontEndPathJson.put("config", frontEndPath + "/config");
		frontEndPathJson.put("dist", frontEndPath + "/dist");
		frontEndPathJson.put("components", frontEndPath + "/src/components");
		frontEndPathJson.put("css", frontEndPath + "/src/css");
		frontEndPathJson.put("image", frontEndPath + "/src/image");
		frontEndPathJson.put("lang", frontEndPath + "/src/lang");
		frontEndPathJson.put("lib", frontEndPath + "/src/lib");
		frontEndPathJson.put("router", frontEndPath + "/src/router");
		frontEndPathJson.put("setup", frontEndPath + "/src/setup");
		frontEndPathJson.put("store", frontEndPath + "/src/store");
		frontEndPathJson.put("vuex", frontEndPath + "/src/vuex");
		//
		pathInfo.put("frontend", frontEndPathJson.toJSONString());
	}

	/**
	 * 开放表信息、预留子类重写
	 *
	 * @param config 配置信息
	 * @return ignore
	 */
	protected List<TableInfo> getAllTableInfoList(ConfigBuilder config) {
		return config.getTableInfoList();
	}

	/**
	 * 预处理配置
	 *
	 * @param config 总配置信息
	 * @return 解析数据结果集
	 */
	protected ConfigBuilder pretreatmentConfigBuilder(ConfigBuilder config) {
		/*
		 * 注入自定义配置
		 */
		if (null != injectionConfig) {
			injectionConfig.initMap();
			config.setInjectionConfig(injectionConfig);
		}
		/*
		 * 表信息列表
		 */
		List<TableInfo> tableList = this.getAllTableInfoList(config);
		for (TableInfo tableInfo : tableList) {
			/* ---------- 添加导入包 ---------- */
			if (config.getGlobalConfig().isActiveRecord()) {
				// 开启 ActiveRecord 模式
				tableInfo.setImportPackages(Model.class.getCanonicalName());
			}
			if (tableInfo.isConvert()) {
				// 表注解
				tableInfo.setImportPackages(TableName.class.getCanonicalName());
			}
			if (config.getStrategyConfig().getLogicDeleteFieldName() != null
					&& tableInfo.isLogicDelete(config.getStrategyConfig().getLogicDeleteFieldName())) {
				// 逻辑删除注解
				tableInfo.setImportPackages(TableLogic.class.getCanonicalName());
			}
			if (StringUtils.isNotBlank(config.getStrategyConfig().getVersionFieldName())) {
				// 乐观锁注解
				tableInfo.setImportPackages(Version.class.getCanonicalName());
			}
			boolean importSerializable = true;
			if (StringUtils.isNotBlank(config.getSuperEntityClass())) {
				// 父实体
				tableInfo.setImportPackages(config.getSuperEntityClass());
				importSerializable = false;
			}
			if (config.getGlobalConfig().isActiveRecord()) {
				importSerializable = true;
			}
			if (importSerializable) {
				tableInfo.setImportPackages(Serializable.class.getCanonicalName());
			}
			// Boolean类型is前缀处理
			if (config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix()
					&& CollectionUtils.isNotEmpty(tableInfo.getFields())) {
				tableInfo.getFields().stream().filter(field -> "boolean".equalsIgnoreCase(field.getPropertyType()))
						.filter(field -> field.getPropertyName().startsWith("is")).forEach(field -> {
							field.setConvert(true);
							field.setPropertyName(
									StringUtils.removePrefixAfterPrefixToLower(field.getPropertyName(), 2));
						});
			}
		}
		return config.setTableInfoList(tableList);
	}

	public InjectionConfig getCfg() {
		return injectionConfig;
	}

	public LeedarsonGenerator setCfg(InjectionConfig injectionConfig) {
		this.injectionConfig = injectionConfig;
		return this;
	}

	public ConfigBuilder getConfig() {
		return config;
	}

	public void setConfig(ConfigBuilder config) {
		this.config = config;
	}

	public InjectionConfig getInjectionConfig() {
		return injectionConfig;
	}

	public void setInjectionConfig(InjectionConfig injectionConfig) {
		this.injectionConfig = injectionConfig;
	}

	public DataSourceConfig getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSourceConfig dataSource) {
		this.dataSource = dataSource;
	}

	public StrategyConfig getStrategy() {
		return strategy;
	}

	public void setStrategy(StrategyConfig strategy) {
		this.strategy = strategy;
	}

	public PackageConfig getPackageInfo() {
		return packageInfo;
	}

	public void setPackageInfo(PackageConfig packageInfo) {
		this.packageInfo = packageInfo;
	}

	public TemplateConfig getTemplate() {
		return template;
	}

	public void setTemplate(TemplateConfig template) {
		this.template = template;
	}

	public GlobalConfig getGlobalConfig() {
		return globalConfig;
	}

	public void setGlobalConfig(GlobalConfig globalConfig) {
		this.globalConfig = globalConfig;
	}

	public AbstractTemplateEngine getTemplateEngine() {
		return templateEngine;
	}

	public void setTemplateEngine(AbstractTemplateEngine templateEngine) {
		this.templateEngine = templateEngine;
	}

	public static Logger getLogger() {
		return logger;
	}

}
